/* Copyright Thanh-Trung Pham, JAIST, 2012 */

package jp.ac.jaist.computation;

import java.util.HashMap;
import java.util.Map;
import jp.ac.jaist.BranchingStructureType;
import jp.ac.jaist.BranchingStructureType.IfPart;
import jp.ac.jaist.CallPropagationType;
import jp.ac.jaist.CallingActivityType;
import jp.ac.jaist.ComponentConnectorType;
import jp.ac.jaist.ComponentInstanceType;
import jp.ac.jaist.InternalActivityType;
import jp.ac.jaist.LoopingStructureType;
import jp.ac.jaist.ParallelStructureType;
import jp.ac.jaist.PrimaryBackupFaultToleranceStructureType;
import jp.ac.jaist.SequentialStructureType;
import jp.ac.jaist.ServiceImplementationType;
import jp.ac.jaist.UserInterface;
import jp.ac.jaist.input.RMInputModelManager;
import jp.ac.jaist.model.RMExecutionModel;
import jp.ac.jaist.model.RMExecutionState;
import jp.ac.jaist.model.RMExecutionTransition;

/**
 *
 * @author Thanh-Trung Pham
 */
public class RMReliabilityCalculator {
    private static final String RM_DOT = ".";

    private static RMReliabilityCalculator m_oCalculator;
    private String m_strUserInterfaceName;
    private Map<String, InternalActivityType> m_oTransformedProvidedServices;

    /**
     * The constructor.
     */
    private RMReliabilityCalculator() {
        m_oTransformedProvidedServices = new HashMap<String, InternalActivityType>();
    }

    /**
     * Get all the transformed provided services.
     * @return all the transformed provided services.
     */
    public Map<String, InternalActivityType> getTransformedProvidedServices() {
        return m_oTransformedProvidedServices;
    }

    /**
     * Get the only instance.
     * @return the only instance.
     */
    public static synchronized RMReliabilityCalculator getInstance() {
        if (m_oCalculator == null) {
            m_oCalculator = new RMReliabilityCalculator();
        }

        return m_oCalculator;
    }

    /**
     * Clear all the cached data.
     */
    private void clearCachedData() {
        m_strUserInterfaceName = null;
        m_oTransformedProvidedServices.clear();
    }

    /**
     * Calculate the reliability-related probabilities for a given user interface.
     * @param userInterfaceName a user interface name.
     * @return an equivalent internal activity with the reliability-related probabilities.
     */
    public synchronized InternalActivityType calculateReliabilityRelatedProbabilities(String userInterfaceName) {
        clearCachedData();

        m_strUserInterfaceName = userInterfaceName;

        RMInputModelManager oInputModelManager = RMInputModelManager.getInstance();
        UserInterface oUserInterface = oInputModelManager.getUserInterfaceByName(userInterfaceName);

        String strComponentInstanceName = oUserInterface.getFrom().getFromComponentInstance();
        String strProvidedServiceName = oUserInterface.getFrom().getFromProvidedService();

        return transformIntoEquivalentIA(strComponentInstanceName, strProvidedServiceName);
    }

    /**
     * Transform a provided service implementation into an equivalent internal activity.
     * @param componentInstanceName the current component instance name.
     * @param providedServiceName a provide service name.
     * @return the equivalent internal activity.
     */
    private InternalActivityType transformIntoEquivalentIA(String componentInstanceName, String providedServiceName) {
        String strFullProvidedServiceName = getFullProvidedServiceName(componentInstanceName, providedServiceName);
        if (m_oTransformedProvidedServices.containsKey(strFullProvidedServiceName)) {
            return m_oTransformedProvidedServices.get(strFullProvidedServiceName);
        }

        RMInputModelManager oInputModelManager = RMInputModelManager.getInstance();

        UserInterface oUserInterface = oInputModelManager.getUserInterfaceByName(m_strUserInterfaceName);
        String strSystemArchitectureName = oUserInterface.getForSystemArchitecture();

        ComponentInstanceType oComponentInstance = oInputModelManager.getComponentInstance(strSystemArchitectureName, componentInstanceName);
        ServiceImplementationType oServiceImplementation = oInputModelManager.getServiceImplementation(oComponentInstance.getForComponent(), providedServiceName);

        InternalActivityType oInternalActivity = transformIntoEquivalentIA(componentInstanceName, oServiceImplementation);

        m_oTransformedProvidedServices.put(strFullProvidedServiceName, oInternalActivity);
        return oInternalActivity;
    }

    /**
     * Transform the call propagation into an equivalent internal activity.
     * @param componentInstanceName the current component instance.
     * @param callPropagation the call propagation.
     * @return the equivalent internal activity.
     */
    private InternalActivityType transformIntoEquivalentIA(String componentInstanceName, CallPropagationType callPropagation) {
        InternalActivityType oInternalActivity = null;

        if (callPropagation.isSetInternalActivity()) {
            oInternalActivity = callPropagation.getInternalActivity();
        } else if (callPropagation.isSetCallingActivity()) {
            oInternalActivity = transformIntoEquivalentIA(componentInstanceName, callPropagation.getCallingActivity());
        } else if (callPropagation.isSetBranchingStructure()) {
            oInternalActivity = transformIntoEquivalentIA(componentInstanceName, callPropagation.getBranchingStructure());
        } else if (callPropagation.isSetLoopingStructure()) {
            oInternalActivity = transformIntoEquivalentIA(componentInstanceName, callPropagation.getLoopingStructure());
        } else if (callPropagation.isSetSequentialStructure()) {
            oInternalActivity = transformIntoEquivalentIA(componentInstanceName, callPropagation.getSequentialStructure());
        } else if (callPropagation.isSetParallelStructure()) {
            oInternalActivity = transformIntoEquivalentIA(componentInstanceName, callPropagation.getParallelStructure());
        } else if (callPropagation.isSetPrimaryBackupFaultToleranceStructure()) {
            oInternalActivity = transformIntoEquivalentIA(componentInstanceName, callPropagation.getPrimaryBackupFaultToleranceStructure());
        }

        return oInternalActivity;
    }

    /**
     * Transform a calling activity into an equivalent internal activity.
     * @param componentInstanceName the current component instance name.
     * @param callingActivity a calling activity.
     * @return the equivalent internal activity.
     */
    private InternalActivityType transformIntoEquivalentIA(String componentInstanceName, CallingActivityType callingActivity) {
        String strCalledService = callingActivity.getCalledService();

        RMInputModelManager oInputModelManager = RMInputModelManager.getInstance();
        String strSystemArchitectureName = oInputModelManager.getUserInterfaceByName(m_strUserInterfaceName).getForSystemArchitecture();

        ComponentConnectorType oComponentConnector = oInputModelManager.getComponentConnectorFor(strSystemArchitectureName, componentInstanceName, strCalledService);
        String strFromComponentInstance = oComponentConnector.getFrom().getFromComponentInstance();
        String strFromProvidedServiceName = oComponentConnector.getFrom().getFromProvidedService();

        return transformIntoEquivalentIA(strFromComponentInstance, strFromProvidedServiceName);
    }

    /**
     * Transform a branching structure into an equivalent internal activity.
     * @param componentInstanceName the current component instance name.
     * @param branchingStructure a branching structure.
     * @return the equivalent internal activity.
     */
    private InternalActivityType transformIntoEquivalentIA(String componentInstanceName, BranchingStructureType branchingStructure) {
        RMExecutionModel oRMExecutionModel = new RMExecutionModel();
        RMExecutionState oSTARTState = oRMExecutionModel.getSTARTState();
        RMExecutionState oENDState = new RMExecutionState();

        RMInputModelManager oInputModelManager = RMInputModelManager.getInstance();

        double dProbabilityOfAllIfBranches = 0.0;
        IfPart[] oIfParts = branchingStructure.getIfPartArray();
        for (IfPart oIfPart : oIfParts) {
            InternalActivityType internalActivity = transformIntoEquivalentIA(componentInstanceName, oIfPart);
            RMExecutionState oState = new RMExecutionState();
            oState.updateStateInformation(internalActivity.getSfp(), internalActivity.getPfp(), internalActivity.getSep(), internalActivity.getNmep());
            oRMExecutionModel.addState(oState);

            String strBranchingCondition = oIfPart.getBranchingCondition();
            Double oBranchingProbability = oInputModelManager.getUsageProfileProbabilityValue(m_strUserInterfaceName, strBranchingCondition);
            dProbabilityOfAllIfBranches += oBranchingProbability.doubleValue();

            RMExecutionTransition oBrachingTransition = new RMExecutionTransition(oSTARTState, oState, oBranchingProbability.doubleValue());
            oRMExecutionModel.addTransition(oBrachingTransition);

            RMExecutionTransition oJoiningTransition = new RMExecutionTransition(oState, oENDState, 1.0);
            oRMExecutionModel.addTransition(oJoiningTransition);
        }

        InternalActivityType internalActivity = transformIntoEquivalentIA(componentInstanceName, branchingStructure.getElsePart());
        RMExecutionState oState = new RMExecutionState();
        oState.updateStateInformation(internalActivity.getSfp(), internalActivity.getPfp(), internalActivity.getSep(), internalActivity.getNmep());
        oRMExecutionModel.addState(oState);

        RMExecutionTransition oBrachingTransition = new RMExecutionTransition(oSTARTState, oState, 1.0 - dProbabilityOfAllIfBranches);
        oRMExecutionModel.addTransition(oBrachingTransition);

        RMExecutionTransition oJoiningTransition = new RMExecutionTransition(oState, oENDState, 1.0);
        oRMExecutionModel.addTransition(oJoiningTransition);

        oRMExecutionModel.addState(oENDState);

        InternalActivityType oInternalActivity = InternalActivityType.Factory.newInstance();
        oInternalActivity.setNmep(oRMExecutionModel.getNmep());
        oInternalActivity.setPfp(oRMExecutionModel.getPfp());
        oInternalActivity.setSep(oRMExecutionModel.getSep());
        oInternalActivity.setSfp(oRMExecutionModel.getSfp());

        return oInternalActivity;
    }

    /**
     * Transform a looping structure into an equivalent internal activity.
     * @param componentInstanceName the current component instance name.
     * @param loopingStructure a looping structure.
     * @return the equivalent internal activity.
     */
    private InternalActivityType transformIntoEquivalentIA(String componentInstanceName, LoopingStructureType loopingStructure) {
        RMExecutionModel oRMExecutionModel = new RMExecutionModel();

        RMExecutionState oCurrentState = oRMExecutionModel.getSTARTState();

        RMInputModelManager oInputModelManager = RMInputModelManager.getInstance();
        Integer oLoopCountAverage = oInputModelManager.getUsageProfileAverageValue(m_strUserInterfaceName, loopingStructure.getLoopCount());

        InternalActivityType internalActivity = transformIntoEquivalentIA(componentInstanceName, loopingStructure.getLoopingPart());
        for (int i = 0; i < oLoopCountAverage.intValue(); i++) {
            RMExecutionState oState = new RMExecutionState();
            oState.updateStateInformation(internalActivity.getSfp(), internalActivity.getPfp(), internalActivity.getSep(), internalActivity.getNmep());
            oRMExecutionModel.addState(oState);

            RMExecutionTransition oTransition = new RMExecutionTransition(oCurrentState, oState, 1.0);
            oRMExecutionModel.addTransition(oTransition);

            oCurrentState = oState;
        }

        RMExecutionState oENDState = new RMExecutionState();
        oRMExecutionModel.addState(oENDState);

        RMExecutionTransition oTransition = new RMExecutionTransition(oCurrentState, oENDState, 1.0);
        oRMExecutionModel.addTransition(oTransition);

        InternalActivityType oInternalActivity = InternalActivityType.Factory.newInstance();
        oInternalActivity.setNmep(oRMExecutionModel.getNmep());
        oInternalActivity.setPfp(oRMExecutionModel.getPfp());
        oInternalActivity.setSep(oRMExecutionModel.getSep());
        oInternalActivity.setSfp(oRMExecutionModel.getSfp());

        return oInternalActivity;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#clone()
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    /**
     * Transform a sequential structure into an equivalent internal activity.
     * @param componentInstanceName the current component instance name.
     * @param loopingStructure a sequential structure.
     * @return the equivalent internal activity.
     */
    private InternalActivityType transformIntoEquivalentIA(String componentInstanceName, SequentialStructureType sequentialStructure) {
        RMExecutionModel oRMExecutionModel = new RMExecutionModel();
        RMExecutionState oCurrentState = oRMExecutionModel.getSTARTState();

        for (int i = 0; i < sequentialStructure.getSequentialPartArray().length; i++) {
            InternalActivityType internalActivity = transformIntoEquivalentIA(componentInstanceName, sequentialStructure.getSequentialPartArray()[i]);

            RMExecutionState oState = new RMExecutionState();
            oState.updateStateInformation(internalActivity.getSfp(), internalActivity.getPfp(), internalActivity.getSep(), internalActivity.getNmep());
            oRMExecutionModel.addState(oState);

            RMExecutionTransition oTransition = new RMExecutionTransition(oCurrentState, oState, 1.0);
            oRMExecutionModel.addTransition(oTransition);

            oCurrentState = oState;
        }

        RMExecutionState oENDState = new RMExecutionState();
        oRMExecutionModel.addState(oENDState);

        RMExecutionTransition oTransition = new RMExecutionTransition(oCurrentState, oENDState, 1.0);
        oRMExecutionModel.addTransition(oTransition);

        InternalActivityType oInternalActivity = InternalActivityType.Factory.newInstance();
        oInternalActivity.setNmep(oRMExecutionModel.getNmep());
        oInternalActivity.setPfp(oRMExecutionModel.getPfp());
        oInternalActivity.setSep(oRMExecutionModel.getSep());
        oInternalActivity.setSfp(oRMExecutionModel.getSfp());

        return oInternalActivity;
    }

    /**
     * Transform a parallel structure into an equivalent internal activity.
     * @param componentInstanceName the current component instance name.
     * @param parallelStructure a parallel structure.
     * @return the equivalent internal activity.
     */
    private InternalActivityType transformIntoEquivalentIA(String componentInstanceName, ParallelStructureType parallelStructure) {
        InternalActivityType[] oInternalActivities = new InternalActivityType[parallelStructure.getParallelPartArray().length];

        for (int i = 0; i < parallelStructure.getParallelPartArray().length; i++) {
            oInternalActivities[i] = transformIntoEquivalentIA(componentInstanceName, parallelStructure.getParallelPartArray()[i]);
        }

        // calculate fp
        double dProduct = 1.0;
        for (int i = 0; i < oInternalActivities.length; i++) {
            dProduct *= (1 - (oInternalActivities[i].getSfp() + oInternalActivities[i].getPfp()));
        }
        double dFp = 1 - dProduct;

        // calculate sfp
        dProduct = 1.0;
        for (int i = 0; i < oInternalActivities.length; i++) {
            dProduct *= (1 - oInternalActivities[i].getSfp());
        }
        double dSfp = 1 - dProduct;

        // calculate pfp
        double dPfp = dFp - dSfp;

        // calculate sep
        dProduct = 1.0;
        for (int i = 0; i < oInternalActivities.length; i++) {
            dProduct *= (1 - oInternalActivities[i].getSep());
        }
        double dSep = 1 - dProduct;

        // calculate nmep
        double dSum = 0.0;
        for (int i = 0; i < oInternalActivities.length; i++) {
            double dFactor1 = 1.0;
            for (int j = 0; j < i; j++) {
                dFactor1 *= (1 - oInternalActivities[j].getSep() - oInternalActivities[j].getNmep());
            }

            double dFactor2 = oInternalActivities[i].getNmep();
            double dFactor3 = 1.0;
            for (int l = i + 1; l < oInternalActivities.length; l++) {
                dFactor3 *= (1 - oInternalActivities[l].getSep());
            }

            dSum += dFactor1 * dFactor2 * dFactor3;
        }
        double dNmep = dSum;

        InternalActivityType oInternalActivity = InternalActivityType.Factory.newInstance();
        oInternalActivity.setNmep(dNmep);
        oInternalActivity.setPfp(dPfp);
        oInternalActivity.setSep(dSep);
        oInternalActivity.setSfp(dSfp);

        return oInternalActivity;
    }

    /**
     * Transform a primary-backup fault tolerance structure into an equivalent internal activity.
     * @param componentInstanceName the current component instance name.
     * @param primaryBackupFaultToleranceStructure a primary-backup fault tolerance structure.
     * @return the equivalent internal activity.
     */
    private InternalActivityType transformIntoEquivalentIA(String componentInstanceName, PrimaryBackupFaultToleranceStructureType primaryBackupFaultToleranceStructure) {
        InternalActivityType[] oInternalActivities = new InternalActivityType[primaryBackupFaultToleranceStructure.getBackupPartArray().length + 1];

        oInternalActivities[0] = transformIntoEquivalentIA(componentInstanceName, primaryBackupFaultToleranceStructure.getPrimaryPart());

        for (int i = 0; i < primaryBackupFaultToleranceStructure.getBackupPartArray().length; i++) {
            oInternalActivities[i + 1] = transformIntoEquivalentIA(componentInstanceName, primaryBackupFaultToleranceStructure.getBackupPartArray()[i]);
        }

        // calculate sfp
        double dProduct = 1.0;
        for (int i = 0; i < oInternalActivities.length; i++) {
            dProduct *= oInternalActivities[i].getSfp();
        }
        double dSfp = dProduct;

        // calculate pfp
        double dSum = 0.0;
        for (int i = 0; i < oInternalActivities.length; i++) {
            double dFactor1 = oInternalActivities[i].getPfp();
            double dFactor2 = 1.0;
            for (int j = 0; j < i; j++) {
                dFactor2 *= oInternalActivities[j].getSfp();
            }

            dSum += dFactor1 * dFactor2;
        }
        double dPfp = dSum;

        // calculate sep
        dProduct = 1.0;
        for (int i = 0; i < oInternalActivities.length; i++) {
            dProduct *= oInternalActivities[i].getSep();
        }
        double dSep = dProduct;

        // calculate nmep
        dSum = 0.0;
        for (int i = 0; i < oInternalActivities.length; i++) {
            double dFactor1 = oInternalActivities[i].getNmep();
            double dFactor2 = 1.0;
            for (int j = 0; j < i; j++) {
                dFactor2 *= oInternalActivities[j].getSep();
            }

            dSum += dFactor1 * dFactor2;
        }

        double dNmep = dSum;

        InternalActivityType oInternalActivity = InternalActivityType.Factory.newInstance();
        oInternalActivity.setNmep(dNmep);
        oInternalActivity.setPfp(dPfp);
        oInternalActivity.setSep(dSep);
        oInternalActivity.setSfp(dSfp);

        return oInternalActivity;
    }

    /**
     * Get the full provided service name.
     * @param componentInstanceName component instance name.
     * @param providedServiceName a provided service name.
     * @return the full provided service name.
     */
    private String getFullProvidedServiceName(String componentInstanceName, String providedServiceName) {
        return componentInstanceName + RM_DOT + providedServiceName;
    }
}
